From 3a35d2f81c3b8de8a0feed96f21f6daf590b397a Mon Sep 17 00:00:00 2001 From: "sos22@douglas.cl.cam.ac.uk" Date: Fri, 12 Nov 2004 16:44:19 +0000 Subject: [PATCH] bitkeeper revision 1.1159.170.10 (4194e863CFMzlWI0nAmHog0_NZ2vfQ) Add in a minimal reimplementation of xend. This is mostly useful for prototyping things which would require wide-spread changes to xend, if implemented there e.g. domain replay. It is emphatically not useful as a replacement for xend in general use. --- .rootkeys | 6 + BitKeeper/etc/ignore | 1 + tools/x2d2/Makefile | 10 + tools/x2d2/cntrl_con.c | 458 +++++++++++++++ tools/x2d2/domain_controller.h | 562 ++++++++++++++++++ tools/x2d2/main.c | 1012 ++++++++++++++++++++++++++++++++ tools/x2d2/minixend.h | 218 +++++++ tools/x2d2/util.c | 132 +++++ 8 files changed, 2399 insertions(+) create mode 100644 tools/x2d2/Makefile create mode 100644 tools/x2d2/cntrl_con.c create mode 100644 tools/x2d2/domain_controller.h create mode 100644 tools/x2d2/main.c create mode 100644 tools/x2d2/minixend.h create mode 100644 tools/x2d2/util.c diff --git a/.rootkeys b/.rootkeys index 2a93a73cac..c6a8030301 100644 --- a/.rootkeys +++ b/.rootkeys @@ -527,6 +527,12 @@ 40fcefb3yMSrZvApO9ToIi-iQwnchA tools/sv/images/xen.png 41013a83z27rKvWIxAfUBMVZ1eDCDg tools/sv/inc/script.js 40fcefb3zGC9XNBkSwTEobCoq8YClA tools/sv/inc/style.css +4194e861IgTabTt8HOuh143QIJFD1Q tools/x2d2/Makefile +4194e861M2gcBz4i94cQYpqzi8n6UA tools/x2d2/cntrl_con.c +4194e861xToz-o2KH6VT_CPJqVGMCg tools/x2d2/domain_controller.h +4194e8612TrrMvC8ZlA4h2ZYCPWz4g tools/x2d2/main.c +4194e861x2eqNCD61RYPCUEBVdMYuw tools/x2d2/minixend.h +4194e861A4V9VbD_FYmgXpYEj5YwVg tools/x2d2/util.c 403a3edbrr8RE34gkbR40zep98SXbg tools/xentrace/Makefile 40a107afN60pFdURgBv9KwEzgRl5mQ tools/xentrace/formats 4050c413PhhLNAYk3TEwP37i_iLw9Q tools/xentrace/xentrace.8 diff --git a/BitKeeper/etc/ignore b/BitKeeper/etc/ignore index 110cc58428..01ffa83198 100644 --- a/BitKeeper/etc/ignore +++ b/BitKeeper/etc/ignore @@ -79,3 +79,4 @@ install/* patches/ebtables-brnf-5_vs_2.4.25.diff patches/ebtables.diff +tools/x2d2/minixend diff --git a/tools/x2d2/Makefile b/tools/x2d2/Makefile new file mode 100644 index 0000000000..e1337bffa5 --- /dev/null +++ b/tools/x2d2/Makefile @@ -0,0 +1,10 @@ +CFLAGS+=-Wall -g -Werror + +SRCS=main.c cntrl_con.c util.c + +all: minixend + +#$(SRCS): minixend.h + +minixend: $(subst .c,.o,$(SRCS)) + gcc $^ -o $@ $(LDFLAGS) -lxc -lpthread diff --git a/tools/x2d2/cntrl_con.c b/tools/x2d2/cntrl_con.c new file mode 100644 index 0000000000..5851d7df38 --- /dev/null +++ b/tools/x2d2/cntrl_con.c @@ -0,0 +1,458 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "minixend.h" + +struct command { + const char *name; + void (*func)(struct open_connection *oc, const struct command *c, + const char *, const char *); +}; + +static void +domain_created(const char *name, int mem_kb, int domid) +{ + struct domain *d; + d = xmalloc(sizeof(*d)); + d->domid = domid; + d->name = xstrdup(name); + d->mem_kb = mem_kb; + d->state = DOM_STATE_CREATED; + d->control_evtchn = -1; /* Not connected yet. */ + + memcpy(d->netif_mac, "\xaa\x00\x00\x02\x00\x00", 6); + d->netif_mac[5] = d->domid; + + pthread_mutex_init(&d->mux, NULL); + pthread_cond_init(&d->cond, NULL); + + pthread_create(&d->thread, NULL, domain_thread_func, d); + + list_insert_after(&d->domain_list, &head_domain); +} + +static struct domain * +find_domain(int domain_id) +{ + struct domain *d; + + foreach_domain(d) { + if (d->domid == domain_id) + return d; + } + return NULL; +} + +static int +free_event_port(struct domain *d, int port) +{ + if (d == NULL) + return xc_evtchn_close(xc_handle, DOMID_SELF, port); + else + return xc_evtchn_close(xc_handle, d->domid, port); +} + +static char * +readline(struct open_connection *oc) +{ + char *end; + char *res; + int line_length; + + if (oc->state == OC_STATE_ERROR) + return NULL; + + end = memchr(oc->buf, '\r', oc->buf_used); + assert(end != NULL); + line_length = end - oc->buf; + + res = xmalloc(line_length + 1); + memcpy(res, oc->buf, line_length); + res[line_length] = 0; + memmove(oc->buf, oc->buf + line_length + 2, + oc->buf_used - line_length - 2); + + oc->buf_used -= line_length + 2; + + if (memchr(oc->buf, '\n', oc->buf_used)) + oc->state = OC_STATE_COMMAND_PENDING; + else + oc->state = OC_STATE_CONNECTED; + + return res; +} + +static unsigned long +find_domain_shared_info_mfn(struct domain *d) +{ + xc_dominfo_t info; + + xc_domain_getinfo(xc_handle, d->domid, 1, &info); + return info.shared_info_frame; +} + +static void +send_message(struct open_connection *oc, const char *fmt, ...) +{ + char *buf; + va_list ap; + int size; + int off; + ssize_t r; + + if (oc->state == OC_STATE_ERROR) + return; + + va_start(ap, fmt); + size = vasprintf(&buf, fmt, ap); + va_end(ap); + if (size < 0) + err(1, "preparing response to a query"); + assert(buf[0] == 'E' || buf[0] == 'N'); + assert(isdigit(buf[1])); + assert(isdigit(buf[2])); + assert(buf[3] == ' ' || buf[3] == '\n'); + + off = 0; + while (off < size) { + r = write(oc->fd, buf + off, size - off); + if (r < 0) { + warn("sending response to remote"); + oc->state = OC_STATE_ERROR; + free(buf); + return; + } + off += r; + } + free(buf); +} + +static void +default_command_handler(struct open_connection *oc, const struct command *ign, + const char *buf, const char *args) +{ + warnx("bad command %s", buf); + send_message(oc, "E00 unknown command %s\n", buf); +} + +static void +create_command_handler(struct open_connection *oc, const struct command *ign, + const char *buf, const char *args) +{ + char *name; + unsigned mem_kb; + int r; + u32 domid = -1; + + r = sscanf(args, "%d %a[^\n]", &mem_kb, &name); + if (r != 2) { + send_message(oc, "E01 failed to parse %s\n", args); + return; + } + r = xc_domain_create(xc_handle, mem_kb, name, -1, 0, &domid); + if (r < 0) { + send_message(oc, "E02 creating domain (%s)\n", + strerror(errno)); + free(name); + return; + } + + domain_created(name, mem_kb, domid); + + send_message(oc, "N00 %d\n", domid); + free(name); +} + +static void +build_command_handler(struct open_connection *oc, const struct command *ign, + const char *buf, const char *args) +{ + struct domain *d; + int domain_id; + char *image, *cmdline; + int event_ports[2]; + int r; + + r = sscanf(args, "%d %a[^\t] %a[^\n]", &domain_id, + &image, &cmdline); + if (r != 3) { + send_message(oc, "E03 failed to parse %s\n", args); + return; + } + d = find_domain(domain_id); + if (d == NULL) { + send_message(oc, "E04 unknown domain %d\n", domain_id); + goto out; + } + if (d->state != DOM_STATE_CREATED) { + send_message(oc, "E05 domain %d in bad state\n", domain_id); + goto out; + } + + r = allocate_event_channel(d, event_ports); + if (r < 0) { + send_message(oc, "E06 allocating control event channel: %s\n", + strerror(errno)); + goto out; + } + + r = xc_linux_build(xc_handle, domain_id, image, NULL, cmdline, + event_ports[1], 0); + if (r < 0) { + send_message(oc, "E07 building domain: %s\n", + strerror(errno)); + free_event_port(NULL, event_ports[0]); + free_event_port(d, event_ports[1]); + goto out; + } + + if (ioctl(evtchn_fd, EVTCHN_BIND, event_ports[0]) < 0) + err(1, "binding to event control event channel"); + + d->shared_info_mfn = find_domain_shared_info_mfn(d); + d->shared_info = map_domain_mem(d, d->shared_info_mfn); + if (d->shared_info == NULL) + err(1, "maping domain shared info page at %lx.\n", + d->shared_info_mfn); + d->ctrl_if = (control_if_t *)((unsigned)d->shared_info + 2048); + + d->control_evtchn = event_ports[0]; + d->state = DOM_STATE_PAUSED; + + send_message(oc, "N00\n"); + + out: + free(image); + free(cmdline); + return; +} + +static void +unpause_command_handler(struct open_connection *oc, + const struct command *ign, + const char *buf, + const char *args) +{ + int domain_id; + int r; + struct domain *d; + + r = sscanf(args, "%d", &domain_id); + if (r != 1) { + send_message(oc, "E08 cannot parse %s\n", args); + return; + } + d = find_domain(domain_id); + if (d == NULL) { + send_message(oc, "E09 cannot find domain %d\n", domain_id); + return; + } + if (d->state != DOM_STATE_PAUSED) { + send_message(oc, "E10 domain not paused\n"); + return; + } + + r = xc_domain_unpause(xc_handle, d->domid); + if (r < 0) { + send_message(oc, "E11 unpausing domain: %s\n", + strerror(errno)); + return; + } + + d->state = DOM_STATE_RUNNING; + send_message(oc, "N00\n"); +} + +static void +console_command_handler(struct open_connection *oc, + const struct command *ign, + const char *buf, + const char *args) +{ + int domain_id; + struct domain *d; + int r; + struct sockaddr_in name; + socklen_t namelen; + + r = sscanf(args, "%d", &domain_id); + if (r != 1) { + send_message(oc, "E12 cannot parse %s\n", args); + return; + } + d = find_domain(domain_id); + if (d == NULL) { + send_message(oc, "E13 cannot find domain %d\n", domain_id); + return; + } + if (d->cc != NULL) { + send_message(oc, "E14 console already exists\n"); + return; + } + + d->cc = xmalloc(sizeof(*d->cc)); + d->cc->fd = socket(PF_INET, SOCK_STREAM, 0); + if (d->cc->fd < 0) + err(1, "creating console socket"); + d->cc->dom = d; + d->cc->state = CC_STATE_PENDING; + d->cc->buf_used = 0; + d->cc->buf_allocated = 0; + d->cc->buf = NULL; + + r = listen(d->cc->fd, 1); + if (r < 0) + err(1, "listening on console socket"); + namelen = sizeof(name); + r = getsockname(d->cc->fd, (struct sockaddr *)&name, &namelen); + if (r < 0) + err(1, "getting name of console socket"); + assert(name.sin_family == AF_INET); + assert(namelen == sizeof(name)); + list_insert_after(&d->cc->list, &head_console); + send_message(oc, "N00 %d\n", ntohs(name.sin_port)); +} + +static void +plug_command_handler(struct open_connection *oc, + const struct command *ign, + const char *buf, + const char *args) +{ + unsigned domid; + int r; + struct domain *d; + + r = sscanf(args, "%d", &domid); + if (r != 1) { + send_message(oc, "E15 cannot parse %s\n", args); + return; + } + d = find_domain(domid); + if (d == NULL) { + send_message(oc, "E16 cannot find domain %d\n", domid); + return; + } + + d->plugged = 1; + send_message(oc, "N00\n"); + PRINTF(1, "set domain %d plug state to %d\n", d->domid, d->plugged); +} + +static void +destroy_command_handler(struct open_connection *oc, + const struct command *ign, + const char *buf, + const char *args) +{ + unsigned domid; + int r; + struct domain *d; + + r = sscanf(args, "%d", &domid); + if (r != 1) { + send_message(oc, "E17 cannot parse %s\n", args); + return; + } + d = find_domain(domid); + if (d == NULL) { + send_message(oc, "E18 cannot find domain %d\n", domid); + return; + } + + r = xc_domain_destroy(xc_handle, domid); + if (r < 0) { + send_message(oc, "E19 error destroying domain %d: %s\n", + domid, sys_errlist[errno]); + return; + } + d->state = DOM_STATE_DEAD; + + send_message(oc, "N00\n"); +} + +static void +list_command_handler(struct open_connection *oc, + const struct command *ign, + const char *buf, + const char *args) +{ + struct domain *d; + static const char *const state_strings[] = { + [DOM_STATE_CREATED] = "created", + [DOM_STATE_PAUSED] = "paused", + [DOM_STATE_RUNNING] = "running", + [DOM_STATE_DEAD] = "dead" + }; + + foreach_domain(d) { + send_message(oc, "N01 %d %s %d %s\n", + d->domid, + d->name, + d->mem_kb, + state_strings[d->state]); + } + send_message(oc, "N00\n"); +} + +static struct command +default_command = { NULL, default_command_handler }; + +static struct command +commands[] = { + { "build", build_command_handler }, + { "console", console_command_handler }, + { "create", create_command_handler }, + { "destroy", destroy_command_handler }, + { "plug", plug_command_handler }, + { "list", list_command_handler }, + { "unpause", unpause_command_handler } +}; + +void +process_command(struct open_connection *oc) +{ + char *buf, *b; + int command_len; + int x; + struct command *cmd; + + buf = readline(oc); + if (buf == NULL) + return; + b = strchr(buf, ' '); + if (b == NULL) + command_len = strlen(buf); + else + command_len = b - buf; + b = buf + command_len; + while (b[0] && b[0] == ' ') + b++; + + cmd = &default_command; + for (x = 0; x < sizeof(commands) / sizeof(commands[0]); x++) { + if (strlen(commands[x].name) == command_len && + memcmp(commands[x].name, buf, command_len) == 0) { + cmd = &commands[x]; + break; + } + } + cmd->func(oc, cmd, buf, b); + free(buf); + return; +} diff --git a/tools/x2d2/domain_controller.h b/tools/x2d2/domain_controller.h new file mode 100644 index 0000000000..14775c07c1 --- /dev/null +++ b/tools/x2d2/domain_controller.h @@ -0,0 +1,562 @@ +/****************************************************************************** + * domain_controller.h + * + * Interface to server controller (e.g., 'xend'). This header file defines the + * interface that is shared with guest OSes. + * + * Copyright (c) 2004, K A Fraser + */ + +/* I've copied this from the xen source pool as getting Xen and + userspace headers to play nicely together is beyond me -- sos22 */ +#define PACKED __attribute__ ((packed)) +typedef unsigned long memory_t; /* Full-sized pointer/address/memory-size. */ +#define __MEMORY_PADDING(_X) u32 __pad_ ## _X +#define _MEMORY_PADDING(_X) __MEMORY_PADDING(_X) +#define MEMORY_PADDING _MEMORY_PADDING(__LINE__) +typedef u16 domid_t; + +/* + * Reason codes for SCHEDOP_shutdown. These are opaque to Xen but may be + * interpreted by control software to determine the appropriate action. These + * are only really advisories: the controller can actually do as it likes. + */ +#define SHUTDOWN_poweroff 0 /* Domain exited normally. Clean up and kill. */ +#define SHUTDOWN_reboot 1 /* Clean up, kill, and then restart. */ +#define SHUTDOWN_suspend 2 /* Clean up, save suspend info, kill. */ + + +/* + * CONTROLLER MESSAGING INTERFACE. + */ + +typedef struct { + u8 type; /* 0: echoed in response */ + u8 subtype; /* 1: echoed in response */ + u8 id; /* 2: echoed in response */ + u8 length; /* 3: number of bytes in 'msg' */ + u8 msg[60]; /* 4: type-specific message data */ +} PACKED control_msg_t; /* 64 bytes */ + +#define CONTROL_RING_SIZE 8 +typedef u32 CONTROL_RING_IDX; +#define MASK_CONTROL_IDX(_i) ((_i)&(CONTROL_RING_SIZE-1)) + +typedef struct { + control_msg_t tx_ring[CONTROL_RING_SIZE]; /* 0: guest -> controller */ + control_msg_t rx_ring[CONTROL_RING_SIZE]; /* 512: controller -> guest */ + CONTROL_RING_IDX tx_req_prod, tx_resp_prod; /* 1024, 1028 */ + CONTROL_RING_IDX rx_req_prod, rx_resp_prod; /* 1032, 1036 */ +} PACKED control_if_t; /* 1040 bytes */ + +/* + * Top-level command types. + */ +#define CMSG_CONSOLE 0 /* Console */ +#define CMSG_BLKIF_BE 1 /* Block-device backend */ +#define CMSG_BLKIF_FE 2 /* Block-device frontend */ +#define CMSG_NETIF_BE 3 /* Network-device backend */ +#define CMSG_NETIF_FE 4 /* Network-device frontend */ +#define CMSG_SHUTDOWN 6 /* Shutdown messages */ +#define CMSG_PDB_BE 7 /* PDB backend. */ +#define CMSG_PDB_FE 8 /* PDB frontend. */ + +/****************************************************************************** + * CONSOLE DEFINITIONS + */ + +/* + * Subtypes for console messages. + */ +#define CMSG_CONSOLE_DATA 0 + + +/****************************************************************************** + * BLOCK-INTERFACE FRONTEND DEFINITIONS + */ + +/* Messages from domain controller to guest. */ +#define CMSG_BLKIF_FE_INTERFACE_STATUS_CHANGED 0 + +/* Messages from guest to domain controller. */ +#define CMSG_BLKIF_FE_DRIVER_STATUS_CHANGED 32 +#define CMSG_BLKIF_FE_INTERFACE_CONNECT 33 +#define CMSG_BLKIF_FE_INTERFACE_DISCONNECT 34 + +/* These are used by both front-end and back-end drivers. */ +#define blkif_vdev_t u16 +#define blkif_pdev_t u16 +#define blkif_sector_t u64 + +/* + * CMSG_BLKIF_FE_INTERFACE_STATUS_CHANGED: + * Notify a guest about a status change on one of its block interfaces. + * If the interface is DESTROYED or DOWN then the interface is disconnected: + * 1. The shared-memory frame is available for reuse. + * 2. Any unacknowledged messgaes pending on the interface were dropped. + */ +#define BLKIF_INTERFACE_STATUS_DESTROYED 0 /* Interface doesn't exist. */ +#define BLKIF_INTERFACE_STATUS_DISCONNECTED 1 /* Exists but is disconnected. */ +#define BLKIF_INTERFACE_STATUS_CONNECTED 2 /* Exists and is connected. */ +typedef struct { + u32 handle; /* 0 */ + u32 status; /* 4 */ + u16 evtchn; /* 8: (only if status == BLKIF_INTERFACE_STATUS_CONNECTED). */ +} PACKED blkif_fe_interface_status_changed_t; /* 10 bytes */ + +/* + * CMSG_BLKIF_FE_DRIVER_STATUS_CHANGED: + * Notify the domain controller that the front-end driver is DOWN or UP. + * When the driver goes DOWN then the controller will send no more + * status-change notifications. When the driver comes UP then the controller + * will send a notification for each interface that currently exists. + * If the driver goes DOWN while interfaces are still UP, the domain + * will automatically take the interfaces DOWN. + */ +#define BLKIF_DRIVER_STATUS_DOWN 0 +#define BLKIF_DRIVER_STATUS_UP 1 +typedef struct { + /* IN */ + u32 status; /* 0: BLKIF_DRIVER_STATUS_??? */ + /* OUT */ + /* + * Tells driver how many interfaces it should expect to immediately + * receive notifications about. + */ + u32 nr_interfaces; /* 4 */ +} PACKED blkif_fe_driver_status_changed_t; /* 8 bytes */ + +/* + * CMSG_BLKIF_FE_INTERFACE_CONNECT: + * If successful, the domain controller will acknowledge with a + * STATUS_CONNECTED message. + */ +typedef struct { + u32 handle; /* 0 */ + u32 __pad; + memory_t shmem_frame; /* 8 */ + MEMORY_PADDING; +} PACKED blkif_fe_interface_connect_t; /* 16 bytes */ + +/* + * CMSG_BLKIF_FE_INTERFACE_DISCONNECT: + * If successful, the domain controller will acknowledge with a + * STATUS_DISCONNECTED message. + */ +typedef struct { + u32 handle; /* 0 */ +} PACKED blkif_fe_interface_disconnect_t; /* 4 bytes */ + + +/****************************************************************************** + * BLOCK-INTERFACE BACKEND DEFINITIONS + */ + +/* Messages from domain controller. */ +#define CMSG_BLKIF_BE_CREATE 0 /* Create a new block-device interface. */ +#define CMSG_BLKIF_BE_DESTROY 1 /* Destroy a block-device interface. */ +#define CMSG_BLKIF_BE_CONNECT 2 /* Connect i/f to remote driver. */ +#define CMSG_BLKIF_BE_DISCONNECT 3 /* Disconnect i/f from remote driver. */ +#define CMSG_BLKIF_BE_VBD_CREATE 4 /* Create a new VBD for an interface. */ +#define CMSG_BLKIF_BE_VBD_DESTROY 5 /* Delete a VBD from an interface. */ +#define CMSG_BLKIF_BE_VBD_GROW 6 /* Append an extent to a given VBD. */ +#define CMSG_BLKIF_BE_VBD_SHRINK 7 /* Remove last extent from a given VBD. */ + +/* Messages to domain controller. */ +#define CMSG_BLKIF_BE_DRIVER_STATUS_CHANGED 32 + +/* + * Message request/response definitions for block-device messages. + */ + +typedef struct { + blkif_sector_t sector_start; /* 0 */ + blkif_sector_t sector_length; /* 8 */ + blkif_pdev_t device; /* 16 */ + u16 __pad; /* 18 */ +} PACKED blkif_extent_t; /* 20 bytes */ + +/* Non-specific 'okay' return. */ +#define BLKIF_BE_STATUS_OKAY 0 +/* Non-specific 'error' return. */ +#define BLKIF_BE_STATUS_ERROR 1 +/* The following are specific error returns. */ +#define BLKIF_BE_STATUS_INTERFACE_EXISTS 2 +#define BLKIF_BE_STATUS_INTERFACE_NOT_FOUND 3 +#define BLKIF_BE_STATUS_INTERFACE_CONNECTED 4 +#define BLKIF_BE_STATUS_VBD_EXISTS 5 +#define BLKIF_BE_STATUS_VBD_NOT_FOUND 6 +#define BLKIF_BE_STATUS_OUT_OF_MEMORY 7 +#define BLKIF_BE_STATUS_EXTENT_NOT_FOUND 8 +#define BLKIF_BE_STATUS_MAPPING_ERROR 9 + +/* This macro can be used to create an array of descriptive error strings. */ +#define BLKIF_BE_STATUS_ERRORS { \ + "Okay", \ + "Non-specific error", \ + "Interface already exists", \ + "Interface not found", \ + "Interface is still connected", \ + "VBD already exists", \ + "VBD not found", \ + "Out of memory", \ + "Extent not found for VBD", \ + "Could not map domain memory" } + +/* + * CMSG_BLKIF_BE_CREATE: + * When the driver sends a successful response then the interface is fully + * created. The controller will send a DOWN notification to the front-end + * driver. + */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Domain attached to new interface. */ + u16 __pad; + u32 blkif_handle; /* 4: Domain-specific interface handle. */ + /* OUT */ + u32 status; /* 8 */ +} PACKED blkif_be_create_t; /* 12 bytes */ + +/* + * CMSG_BLKIF_BE_DESTROY: + * When the driver sends a successful response then the interface is fully + * torn down. The controller will send a DESTROYED notification to the + * front-end driver. + */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Identify interface to be destroyed. */ + u16 __pad; + u32 blkif_handle; /* 4: ...ditto... */ + /* OUT */ + u32 status; /* 8 */ +} PACKED blkif_be_destroy_t; /* 12 bytes */ + +/* + * CMSG_BLKIF_BE_CONNECT: + * When the driver sends a successful response then the interface is fully + * connected. The controller will send a CONNECTED notification to the + * front-end driver. + */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Domain attached to new interface. */ + u16 __pad; + u32 blkif_handle; /* 4: Domain-specific interface handle. */ + memory_t shmem_frame; /* 8: Page cont. shared comms window. */ + MEMORY_PADDING; + u32 evtchn; /* 16: Event channel for notifications. */ + /* OUT */ + u32 status; /* 20 */ +} PACKED blkif_be_connect_t; /* 24 bytes */ + +/* + * CMSG_BLKIF_BE_DISCONNECT: + * When the driver sends a successful response then the interface is fully + * disconnected. The controller will send a DOWN notification to the front-end + * driver. + */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Domain attached to new interface. */ + u16 __pad; + u32 blkif_handle; /* 4: Domain-specific interface handle. */ + /* OUT */ + u32 status; /* 8 */ +} PACKED blkif_be_disconnect_t; /* 12 bytes */ + +/* CMSG_BLKIF_BE_VBD_CREATE */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Identify blkdev interface. */ + u16 __pad; + u32 blkif_handle; /* 4: ...ditto... */ + blkif_vdev_t vdevice; /* 8: Interface-specific id for this VBD. */ + u16 readonly; /* 10: Non-zero -> VBD isn't writable. */ + /* OUT */ + u32 status; /* 12 */ +} PACKED blkif_be_vbd_create_t; /* 16 bytes */ + +/* CMSG_BLKIF_BE_VBD_DESTROY */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Identify blkdev interface. */ + u16 __pad0; /* 2 */ + u32 blkif_handle; /* 4: ...ditto... */ + blkif_vdev_t vdevice; /* 8: Interface-specific id of the VBD. */ + u16 __pad1; /* 10 */ + /* OUT */ + u32 status; /* 12 */ +} PACKED blkif_be_vbd_destroy_t; /* 16 bytes */ + +/* CMSG_BLKIF_BE_VBD_GROW */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Identify blkdev interface. */ + u16 __pad0; /* 2 */ + u32 blkif_handle; /* 4: ...ditto... */ + blkif_extent_t extent; /* 8: Physical extent to append to VBD. */ + blkif_vdev_t vdevice; /* 28: Interface-specific id of the VBD. */ + u16 __pad1; /* 30 */ + /* OUT */ + u32 status; /* 32 */ +} PACKED blkif_be_vbd_grow_t; /* 36 bytes */ + +/* CMSG_BLKIF_BE_VBD_SHRINK */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Identify blkdev interface. */ + u16 __pad0; /* 2 */ + u32 blkif_handle; /* 4: ...ditto... */ + blkif_vdev_t vdevice; /* 8: Interface-specific id of the VBD. */ + u16 __pad1; /* 10 */ + /* OUT */ + u32 status; /* 12 */ +} PACKED blkif_be_vbd_shrink_t; /* 16 bytes */ + +/* + * CMSG_BLKIF_BE_DRIVER_STATUS_CHANGED: + * Notify the domain controller that the back-end driver is DOWN or UP. + * If the driver goes DOWN while interfaces are still UP, the controller + * will automatically send DOWN notifications. + */ +typedef struct { + u32 status; /* 0: BLKIF_DRIVER_STATUS_??? */ +} PACKED blkif_be_driver_status_changed_t; /* 4 bytes */ + + +/****************************************************************************** + * NETWORK-INTERFACE FRONTEND DEFINITIONS + */ + +/* Messages from domain controller to guest. */ +#define CMSG_NETIF_FE_INTERFACE_STATUS_CHANGED 0 + +/* Messages from guest to domain controller. */ +#define CMSG_NETIF_FE_DRIVER_STATUS_CHANGED 32 +#define CMSG_NETIF_FE_INTERFACE_CONNECT 33 +#define CMSG_NETIF_FE_INTERFACE_DISCONNECT 34 + +/* + * CMSG_NETIF_FE_INTERFACE_STATUS_CHANGED: + * Notify a guest about a status change on one of its network interfaces. + * If the interface is DESTROYED or DOWN then the interface is disconnected: + * 1. The shared-memory frame is available for reuse. + * 2. Any unacknowledged messgaes pending on the interface were dropped. + */ +#define NETIF_INTERFACE_STATUS_DESTROYED 0 /* Interface doesn't exist. */ +#define NETIF_INTERFACE_STATUS_DISCONNECTED 1 /* Exists but is disconnected. */ +#define NETIF_INTERFACE_STATUS_CONNECTED 2 /* Exists and is connected. */ +typedef struct { + u32 handle; /* 0 */ + u32 status; /* 4 */ + u16 evtchn; /* 8: status == NETIF_INTERFACE_STATUS_CONNECTED */ + u8 mac[6]; /* 10: status == NETIF_INTERFACE_STATUS_CONNECTED */ +} PACKED netif_fe_interface_status_changed_t; /* 16 bytes */ + +/* + * CMSG_NETIF_FE_DRIVER_STATUS_CHANGED: + * Notify the domain controller that the front-end driver is DOWN or UP. + * When the driver goes DOWN then the controller will send no more + * status-change notifications. When the driver comes UP then the controller + * will send a notification for each interface that currently exists. + * If the driver goes DOWN while interfaces are still UP, the domain + * will automatically take the interfaces DOWN. + */ +#define NETIF_DRIVER_STATUS_DOWN 0 +#define NETIF_DRIVER_STATUS_UP 1 +typedef struct { + /* IN */ + u32 status; /* 0: NETIF_DRIVER_STATUS_??? */ + /* OUT */ + /* + * Tells driver how many interfaces it should expect to immediately + * receive notifications about. + */ + u32 nr_interfaces; /* 4 */ +} PACKED netif_fe_driver_status_changed_t; /* 8 bytes */ + +/* + * CMSG_NETIF_FE_INTERFACE_CONNECT: + * If successful, the domain controller will acknowledge with a + * STATUS_CONNECTED message. + */ +typedef struct { + u32 handle; /* 0 */ + u32 __pad; /* 4 */ + memory_t tx_shmem_frame; /* 8 */ + MEMORY_PADDING; + memory_t rx_shmem_frame; /* 16 */ + MEMORY_PADDING; +} PACKED netif_fe_interface_connect_t; /* 24 bytes */ + +/* + * CMSG_NETIF_FE_INTERFACE_DISCONNECT: + * If successful, the domain controller will acknowledge with a + * STATUS_DISCONNECTED message. + */ +typedef struct { + u32 handle; /* 0 */ +} PACKED netif_fe_interface_disconnect_t; /* 4 bytes */ + + +/****************************************************************************** + * NETWORK-INTERFACE BACKEND DEFINITIONS + */ + +/* Messages from domain controller. */ +#define CMSG_NETIF_BE_CREATE 0 /* Create a new net-device interface. */ +#define CMSG_NETIF_BE_DESTROY 1 /* Destroy a net-device interface. */ +#define CMSG_NETIF_BE_CONNECT 2 /* Connect i/f to remote driver. */ +#define CMSG_NETIF_BE_DISCONNECT 3 /* Disconnect i/f from remote driver. */ + +/* Messages to domain controller. */ +#define CMSG_NETIF_BE_DRIVER_STATUS_CHANGED 32 + +/* + * Message request/response definitions for net-device messages. + */ + +/* Non-specific 'okay' return. */ +#define NETIF_BE_STATUS_OKAY 0 +/* Non-specific 'error' return. */ +#define NETIF_BE_STATUS_ERROR 1 +/* The following are specific error returns. */ +#define NETIF_BE_STATUS_INTERFACE_EXISTS 2 +#define NETIF_BE_STATUS_INTERFACE_NOT_FOUND 3 +#define NETIF_BE_STATUS_INTERFACE_CONNECTED 4 +#define NETIF_BE_STATUS_OUT_OF_MEMORY 5 +#define NETIF_BE_STATUS_MAPPING_ERROR 6 + +/* This macro can be used to create an array of descriptive error strings. */ +#define NETIF_BE_STATUS_ERRORS { \ + "Okay", \ + "Non-specific error", \ + "Interface already exists", \ + "Interface not found", \ + "Interface is still connected", \ + "Out of memory", \ + "Could not map domain memory" } + +/* + * CMSG_NETIF_BE_CREATE: + * When the driver sends a successful response then the interface is fully + * created. The controller will send a DOWN notification to the front-end + * driver. + */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Domain attached to new interface. */ + u16 __pad0; /* 2 */ + u32 netif_handle; /* 4: Domain-specific interface handle. */ + u8 mac[6]; /* 8 */ + u16 __pad1; /* 14 */ + /* OUT */ + u32 status; /* 16 */ +} PACKED netif_be_create_t; /* 20 bytes */ + +/* + * CMSG_NETIF_BE_DESTROY: + * When the driver sends a successful response then the interface is fully + * torn down. The controller will send a DESTROYED notification to the + * front-end driver. + */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Identify interface to be destroyed. */ + u16 __pad; + u32 netif_handle; /* 4: ...ditto... */ + /* OUT */ + u32 status; /* 8 */ +} PACKED netif_be_destroy_t; /* 12 bytes */ + +/* + * CMSG_NETIF_BE_CONNECT: + * When the driver sends a successful response then the interface is fully + * connected. The controller will send a CONNECTED notification to the + * front-end driver. + */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Domain attached to new interface. */ + u16 __pad0; /* 2 */ + u32 netif_handle; /* 4: Domain-specific interface handle. */ + memory_t tx_shmem_frame; /* 8: Page cont. tx shared comms window. */ + MEMORY_PADDING; + memory_t rx_shmem_frame; /* 16: Page cont. rx shared comms window. */ + MEMORY_PADDING; + u16 evtchn; /* 24: Event channel for notifications. */ + u16 __pad1; /* 26 */ + /* OUT */ + u32 status; /* 28 */ +} PACKED netif_be_connect_t; /* 32 bytes */ + +/* + * CMSG_NETIF_BE_DISCONNECT: + * When the driver sends a successful response then the interface is fully + * disconnected. The controller will send a DOWN notification to the front-end + * driver. + */ +typedef struct { + /* IN */ + domid_t domid; /* 0: Domain attached to new interface. */ + u16 __pad; + u32 netif_handle; /* 4: Domain-specific interface handle. */ + /* OUT */ + u32 status; /* 8 */ +} PACKED netif_be_disconnect_t; /* 12 bytes */ + +/* + * CMSG_NETIF_BE_DRIVER_STATUS_CHANGED: + * Notify the domain controller that the back-end driver is DOWN or UP. + * If the driver goes DOWN while interfaces are still UP, the domain + * will automatically send DOWN notifications. + */ +typedef struct { + u32 status; /* 0: NETIF_DRIVER_STATUS_??? */ +} PACKED netif_be_driver_status_changed_t; /* 4 bytes */ + + +/****************************************************************************** + * SHUTDOWN DEFINITIONS + */ + +/* + * Subtypes for shutdown messages. + */ +#define CMSG_SHUTDOWN_POWEROFF 0 /* Clean shutdown (SHUTDOWN_poweroff). */ +#define CMSG_SHUTDOWN_REBOOT 1 /* Clean shutdown (SHUTDOWN_reboot). */ +#define CMSG_SHUTDOWN_SUSPEND 2 /* Create suspend info, then */ + /* SHUTDOWN_suspend. */ + +/****************************************************************************** + * PDB DEFINITIONS + */ + +/* Notify the backend that a new frontend has connected. */ +#define CMSG_PDB_BE_INTERFACE_CONNECTED 0 +typedef struct { + int assist_port; + int event_port; +} pdb_be_connected_t; + +/* Notify the domain controller that the status of the backend has + changed. */ +#define CMSG_PDB_BE_DRIVER_STATUS_CHANGED 1 +typedef struct { + unsigned status; +#define PDB_DRIVER_STATUS_UP 1 + unsigned long event_page; + unsigned long assist_page; +} pdb_be_driver_status_changed_t; + +/* Notify a front end that a back end just popped up. */ +#define CMSG_PDB_FE_NEW_BE 0 +typedef struct { + int domain; + int assist_evtchn; + int event_evtchn; + unsigned long assist_frame; + unsigned long event_frame; +} pdb_fe_new_be_t; diff --git a/tools/x2d2/main.c b/tools/x2d2/main.c new file mode 100644 index 0000000000..62844a6572 --- /dev/null +++ b/tools/x2d2/main.c @@ -0,0 +1,1012 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "minixend.h" + +#define MINIXEND_PORT 5123 + +#define mb() asm volatile ("" ::: "memory") + +static void send_control_message(int type, int subtype, int id, + int size, void *payload, + struct domain *target); + +struct list_head +head_domain = LIST_HEAD(&head_domain); + +static struct list_head +head_connection = LIST_HEAD(&head_connection); + +struct list_head +head_console = LIST_HEAD(&head_console); + +#define foreach_open_connection(d) \ +foreach_item(d, &head_connection, struct open_connection, connection_list) + +/* Not modified after initial start up */ +static struct domain * +dom0; +unsigned +xc_handle; +static int +listen_fd; +int +evtchn_fd; + +static struct list_head +head_event_receiver = LIST_HEAD(&head_event_receiver); + +struct event_receiver { + struct list_head list; + int id; + pthread_cond_t cond; +}; + +/* We're protected by the dom0 mutex in here */ +static struct event_receiver * +allocate_event_receiver(struct domain *d) +{ + static int next_message_id; + struct event_receiver *work; + + assert(d == dom0); + work = xmalloc(sizeof(*work)); + work->id = next_message_id++; + pthread_cond_init(&work->cond, NULL); + + list_insert_after(&work->list, &head_event_receiver); + + return work; +} + +static struct event_receiver * +find_event_receiver(int id) +{ + struct event_receiver *work; + foreach_item(work, &head_event_receiver, struct event_receiver, list) + if (work->id == id) + return work; + return NULL; +} + +static void +release_event_receiver(struct event_receiver *w) +{ + list_remove(&w->list); + pthread_cond_destroy(&w->cond); + free(w); +} + +/* Send a message to dom0, and then block awaiting a reply. */ +/* Make sure we don't hold any domain mutexs */ +static void +send_dom0_message_block(control_msg_t *msg) +{ + CONTROL_RING_IDX c; + struct event_receiver *er; + control_msg_t buf; + + PRINTF(0, "sending message to dom0 and blocking for reply.\n"); + pthread_mutex_lock(&dom0->mux); + PRINTF(0, "got dom0 lock.\n"); + er = allocate_event_receiver(dom0); + PRINTF(0, "allocated evetn receiver.\n"); + msg->id = er->id; + PRINTF(1, "sending message with id %d\n", msg->id); + send_control_message(msg->type, msg->subtype, + msg->id, msg->length, msg->msg, dom0); + xc_evtchn_send(xc_handle, dom0->control_evtchn); + + PRINTF(0, "waiting for reply\n"); + pthread_cond_wait(&er->cond, &dom0->mux); + PRINTF(0, "got reply\n"); + + c = dom0->rx_resp_cons % CONTROL_RING_SIZE; + memcpy(&buf, &dom0->ctrl_if->rx_ring[c], sizeof(buf)); + assert(msg->id == buf.id); + assert(msg->type == buf.type); + assert(msg->subtype == buf.subtype); + memcpy(msg, &buf, sizeof(*msg)); + dom0->rx_resp_cons++; + + release_event_receiver(er); + + pthread_mutex_unlock(&dom0->mux); + + PRINTF(1, "got reply to message with id %d\n", msg->id); +} + +/* Allocate an interdomain event channel. event_ports[0] is the + local event port number, event_ports[1] the remote */ +int +allocate_event_channel(struct domain *d, int event_ports[2]) +{ + return xc_evtchn_bind_interdomain(xc_handle, DOMID_SELF, + d->domid, event_ports, + event_ports+1); +} + +static void +accept_new_connection(void) +{ + int fd; + struct open_connection *oc; + + fd = accept(listen_fd, NULL, NULL); + if (fd < 0) + return; + oc = xmalloc(sizeof(*oc)); + oc->fd = fd; + oc->state = OC_STATE_CONNECTED; + oc->buf_used = 0; + oc->buf_allocated = 16; + oc->buf = xmalloc(oc->buf_allocated); + list_insert_after(&oc->connection_list, &head_connection); +} + +static void +closedown_connection(struct open_connection *oc) +{ + close(oc->fd); + assert(oc->buf); + free(oc->buf); + free(oc); +} + +#if 0 +/* Hackl for the benefit of domain replay */ +static unsigned +report_work(u32 *ptr, u32 val, unsigned dom, int do_direct) +{ + if (!do_direct) { + int rc; + asm("int $0x80" : "=a" (rc) + : "0" (264), "b" (ptr), "c" (val), "d" (dom)); + if (rc < 0) { + errno = -rc; + rc = -1; + } + return rc; + } else { + *ptr = val; + return 0; + } +} +#else +static unsigned +report_work(u32 *ptr, u32 val, unsigned dom, int do_direct) +{ + *ptr = val; + return 0; +} +#endif + +static void +send_control_reply(const control_msg_t *msg, struct domain *d) +{ + CONTROL_RING_IDX c; + + PRINTF(3,"Control reply, type %d:%d, length %d.\n", + msg->type, msg->subtype, msg->length); + c = d->ctrl_if->tx_resp_prod % CONTROL_RING_SIZE; + memcpy(&d->ctrl_if->tx_ring[c], msg, sizeof(*msg)); + report_work(&d->ctrl_if->tx_resp_prod, + d->ctrl_if->tx_resp_prod + 1, + d->domid, + 0); + PRINTF(4,"tx_resp_prod %ld.\n", d->ctrl_if->tx_resp_prod); + assert(!d->plugged); +} + +static void +send_trivial_control_reply(const control_msg_t *msg, struct domain *d) +{ + control_msg_t rep; + + memset(&rep, 0, sizeof(rep)); + rep.type = msg->type; + rep.subtype = msg->subtype; + rep.id = msg->id; + send_control_reply(&rep, d); +} + +static void +process_console_control_message(control_msg_t *m, struct domain *d) +{ + int off; + int r; + + if (m->subtype != CMSG_CONSOLE_DATA) { + warnx("unknown console message subtype %d", + m->subtype); + return; + } + + if (m->length > 60) { + warnx("truncating message from domain %d (was length %d)", + d->domid, m->length); + m->length = 60; + } + PRINTF(1, "DOM%d: %.*s\n", d->domid, m->length, m->msg); + send_trivial_control_reply(m, d); + + if (d->cc) { + PRINTF(5, "Have a console connection.\n"); + if (d->cc->state == CC_STATE_CONNECTED) { + PRINTF(5, "Console is connected, sending directly.\n"); + for (off = 0; off < m->length; off += r) { + r = write(d->cc->fd, m->msg + off, + m->length - off); + if (r <= 0) { + d->cc->state = CC_STATE_ERROR; + break; + } + } + } else { + PRINTF(5, "Console not connected, buffering.\n"); + if (d->cc->buf_allocated == 0) { + d->cc->buf_allocated = 60; + d->cc->buf = xmalloc(d->cc->buf_allocated); + d->cc->buf_used = 0; + } else if (d->cc->buf_allocated < + d->cc->buf_used + m->length) { + d->cc->buf_allocated += 60; + d->cc->buf = xrealloc(d->cc->buf, + d->cc->buf_allocated); + } + assert(d->cc->buf_allocated >= + d->cc->buf_used + m->length); + memcpy(d->cc->buf + d->cc->buf_used, + m->msg, + m->length); + d->cc->buf_used += m->length; + } + } +} + +static void +process_blkif_fe_message(control_msg_t *m, struct domain *d) +{ + switch (m->subtype) { + default: + warnx("unknown blkif front end message subtype %d", + m->subtype); + } +} + +static void +send_control_message(int type, int subtype, int id, + int size, void *payload, struct domain *target) +{ + control_msg_t msg; + CONTROL_RING_IDX c; + + msg.type = type; + msg.subtype = subtype; + msg.id = id; + msg.length = size; + memcpy(msg.msg, payload, size); + + c = target->ctrl_if->rx_req_prod % CONTROL_RING_SIZE; + memcpy(&target->ctrl_if->rx_ring[c], &msg, sizeof(msg)); + report_work(&target->ctrl_if->rx_req_prod, + target->ctrl_if->rx_req_prod + 1, + target->domid, + 0); + assert(!target->plugged); +} + +/* Procedure for bringing a new netif front end up: + + -- Front end sends us NETIF_FE_DRIVER_STATUS_CHANGED + -- We send back end NETIF_BE_CREATE, wait for a reply + -- Back end creates a new netif for us, replies + -- We send front end a NETIF_FE_DRIVER_STATUS_CHANGED message saying + how many interfaces we've created for it + -- We send front end a NETIF_FE_INTERFACE_STATUS_CHANGED for each + netif created + -- Front end sends us a NETIF_FE_INTERFACE_CONNECT for each netif +*/ +static void +handle_netif_fe_driver_status_changed(control_msg_t *m, + netif_fe_driver_status_changed_t *sh, + struct domain *d) +{ + netif_fe_interface_status_changed_t if_s; + control_msg_t be_msg; + netif_be_create_t *be = (void *)be_msg.msg; + int r; + + switch (sh->status) { + case NETIF_DRIVER_STATUS_UP: + /* Tell the back end about the new interface coming + * up. */ + if (d->created_netif_backend) { + PRINTF(10, "Front end came up twice in dom %d -> reporting no interfaces this time around.\n", d->domid); + sh->nr_interfaces = 0; + send_control_reply(m, d); + send_control_message(CMSG_NETIF_FE, + CMSG_NETIF_FE_DRIVER_STATUS_CHANGED, + 1, + sizeof(*sh), + sh, + d); + return; + } + be_msg.type = CMSG_NETIF_BE; + be_msg.subtype = CMSG_NETIF_BE_CREATE; + be_msg.id = d->domid; + be_msg.length = sizeof(*be); + be->domid = d->domid; + be->netif_handle = 0; + memcpy(be->mac, d->netif_mac, 6); + + PRINTF(2,"Telling back end about new front end.\n"); + pthread_mutex_unlock(&d->mux); + send_dom0_message_block(&be_msg); + pthread_mutex_lock(&d->mux); + PRINTF(3,"Done.\n"); + + if (be->status != NETIF_BE_STATUS_OKAY) { + /* Uh oh... can't bring back end + * up. */ + sh->nr_interfaces = 0; + send_control_reply(m, d); + send_control_message(CMSG_NETIF_FE, + CMSG_NETIF_FE_DRIVER_STATUS_CHANGED, + 1, + sizeof(*sh), + sh, + d); + return; + } + d->created_netif_backend = 1; + + r = our_system("/etc/xen/vif-bridge up domain=%s mac=%.02x:%.02x:%.02x:%.02x:%.02x:%.02x vif=vif%d.0 bridge=xen-br0", + d->name, + d->netif_mac[0], + d->netif_mac[1], + d->netif_mac[2], + d->netif_mac[3], + d->netif_mac[4], + d->netif_mac[5], + d->domid); + if (r != 0) + warn("error %d running vif-bridge script", r); + + /* Tell domain how many interfaces it has to deal + * with. */ + sh->nr_interfaces = 1; + send_control_reply(m, d); + send_control_message(CMSG_NETIF_FE, + CMSG_NETIF_FE_DRIVER_STATUS_CHANGED, + 1, + sizeof(*sh), + sh, + d); + + PRINTF(2,"Telling front end about its interfaces.\n"); + if_s.handle = 0; + if_s.status = NETIF_INTERFACE_STATUS_DISCONNECTED; + send_control_message(CMSG_NETIF_FE, + CMSG_NETIF_FE_INTERFACE_STATUS_CHANGED, + 1, + sizeof(if_s), + &if_s, + d); + PRINTF(3,"Done.\n"); + + break; + default: + warnx("unknown netif status %ld", sh->status); + break; + } +} + +static void +handle_netif_fe_interface_connect(control_msg_t *m, + netif_fe_interface_connect_t *ic, + struct domain *d) +{ + control_msg_t be_msg; + netif_be_connect_t *bmsg = (void *)be_msg.msg; + netif_fe_interface_status_changed_t fmsg = {0}; + int evtchn_ports[2]; + int r; + + PRINTF(4, "front end sent us an interface connect message.\n"); + send_trivial_control_reply(m, d); + + r = xc_evtchn_bind_interdomain(xc_handle, + dom0->domid, + d->domid, + &evtchn_ports[0], + &evtchn_ports[1]); + if (r < 0) + err(1, "allocating network event channel"); + + be_msg.type = CMSG_NETIF_BE; + be_msg.subtype = CMSG_NETIF_BE_CONNECT; + be_msg.id = 0; + be_msg.length = sizeof(*bmsg); + bmsg->domid = d->domid; + bmsg->netif_handle = ic->handle; + bmsg->tx_shmem_frame = ic->tx_shmem_frame; + bmsg->rx_shmem_frame = ic->rx_shmem_frame; + bmsg->evtchn = evtchn_ports[0]; + + pthread_mutex_unlock(&d->mux); + send_dom0_message_block(&be_msg); + pthread_mutex_lock(&d->mux); + + if (bmsg->status != NETIF_BE_STATUS_OKAY) { + PRINTF(2, "error connected backend netif: %ld\n", + bmsg->status); + abort(); /* Need to handle this */ + } else { + PRINTF(3, "connect backend netif\n"); + + /* Tell the domain that we've connected it up. */ + fmsg.handle = ic->handle; + fmsg.status = NETIF_INTERFACE_STATUS_CONNECTED; + fmsg.evtchn = evtchn_ports[1]; + memcpy(fmsg.mac, d->netif_mac, 6); + + send_control_message(CMSG_NETIF_FE, + CMSG_NETIF_FE_INTERFACE_STATUS_CHANGED, + 0, + sizeof(fmsg), + &fmsg, + d); + } +} + +static void +process_netif_fe_message(control_msg_t *m, struct domain *d) +{ + switch (m->subtype) { + case CMSG_NETIF_FE_DRIVER_STATUS_CHANGED: + { + netif_fe_driver_status_changed_t *sh = + (netif_fe_driver_status_changed_t *)m->msg; + handle_netif_fe_driver_status_changed(m, sh, d); + break; + } + case CMSG_NETIF_FE_INTERFACE_CONNECT: + { + netif_fe_interface_connect_t *ic = + (netif_fe_interface_connect_t *)m->msg; + handle_netif_fe_interface_connect(m, ic, d); + break; + } + default: + warnx("unknown netif front end message subtype %d", + m->subtype); + } +} + +static void +process_pdb_be_driver_status_changed_message(control_msg_t *msg, + pdb_be_driver_status_changed_t*pe, + struct domain *d) +{ + pdb_be_connected_t conn; + pdb_fe_new_be_t new_be; + int assist_channel[2]; + int event_channel[2]; + int r; + + switch (pe->status) { + case PDB_DRIVER_STATUS_UP: + PRINTF(4, "creating event channel for PDB device\n"); + r = allocate_event_channel(d, assist_channel); + r |= allocate_event_channel(d, event_channel); + if (r < 0) + abort(); /* XXX need to handle this */ + + send_trivial_control_reply(msg, d); + + PRINTF(4, "informing front end of event channel\n"); + conn.assist_port = assist_channel[1]; + conn.event_port = event_channel[1]; + send_control_message(CMSG_PDB_BE, + CMSG_PDB_BE_INTERFACE_CONNECTED, + 0, + sizeof(conn), + &conn, + d); + + PRINTF(4, "informing back end of front end\n"); + new_be.domain = d->domid; + new_be.assist_evtchn = assist_channel[0]; + new_be.event_evtchn = event_channel[0]; + new_be.assist_frame = pe->assist_page; + new_be.event_frame = pe->event_page; + send_control_message(CMSG_PDB_FE, + CMSG_PDB_FE_NEW_BE, + 0, + sizeof(new_be), + &new_be, + dom0); + break; + default: + warnx("unknown pdb status %d", pe->status); + } +} + +static void +process_pdb_be_message(control_msg_t *msg, struct domain *d) +{ + switch (msg->subtype) { + case CMSG_PDB_BE_DRIVER_STATUS_CHANGED: + { + pdb_be_driver_status_changed_t *pe = + (pdb_be_driver_status_changed_t *)msg->msg; + process_pdb_be_driver_status_changed_message(msg, pe, d); + break; + } + default: + warnx("unknown pdb back end message subtype %d", + msg->subtype); + } +} + +static void +process_control_message(control_msg_t *msg, struct domain *d) +{ + control_msg_t m; + + /* Don't want a malicous domain messing us about, so copy the + control mesasge into a local buffer. */ + memcpy(&m, msg, sizeof(m)); + switch (m.type) { + case CMSG_CONSOLE: + process_console_control_message(&m, d); + break; + case CMSG_BLKIF_FE: + process_blkif_fe_message(&m, d); + break; + case CMSG_NETIF_FE: + process_netif_fe_message(&m, d); + break; + case CMSG_PDB_BE: + process_pdb_be_message(&m, d); + break; + default: + warnx("unknown control message type %d", m.type); + } +} + +static void +domain_did_control_event(struct domain *d) +{ + CONTROL_RING_IDX c; + + /* Pick up and process control ring messages. */ + while (d->tx_req_cons != d->ctrl_if->tx_req_prod) { + c = d->tx_req_cons % CONTROL_RING_SIZE; + process_control_message(&d->ctrl_if->tx_ring[c], d); + d->tx_req_cons++; + assert(d->tx_req_cons <= d->ctrl_if->tx_req_prod); + PRINTF(5, "req_cons %ld, req_prod %ld.\n", + d->tx_req_cons, d->ctrl_if->tx_req_prod); + } + + /* Take any replies off, and discard them. */ + if (d->rx_resp_cons != d->ctrl_if->rx_resp_prod) + PRINTF(1, "discard %ld events\n", + d->ctrl_if->rx_resp_prod - + d->rx_resp_cons); + d->rx_resp_cons = d->ctrl_if->rx_resp_prod; +} + +/* This is the main function for domain control threads */ +void * +domain_thread_func(void *D) +{ + struct domain *d = D; + int r; + CONTROL_RING_IDX old_resp_prod, old_req_prod; + + pthread_mutex_lock(&d->mux); + for (;;) { + pthread_cond_wait(&d->cond, &d->mux); + + old_resp_prod = d->ctrl_if->tx_resp_prod; + old_req_prod = d->ctrl_if->rx_req_prod; + + domain_did_control_event(d); + if (d->cc && d->cc->in_buf_used != 0 && d->plugged == 0) { + r = d->cc->in_buf_used; + if (r > 60) + r = 60; + PRINTF(1, "Sending to domain: %.*s\n", + r, d->cc->in_buf); + send_control_message(CMSG_CONSOLE, + CMSG_CONSOLE_DATA, + 0, + r, + d->cc->in_buf, + d); + memmove(d->cc->in_buf, d->cc->in_buf + r, + d->cc->in_buf_used - r); + d->cc->in_buf_used -= r; + } + + if (d->ctrl_if->tx_resp_prod != old_resp_prod || + d->ctrl_if->rx_req_prod != old_req_prod) + xc_evtchn_send(xc_handle, d->control_evtchn); + } +} + +/* This is the only thing you can do with a domain structure if you're + not in the thread which controls that domain. Domain 0 is + special. */ +void +signal_domain(struct domain *d) +{ + CONTROL_RING_IDX c; + int id; + struct event_receiver *evt; + + pthread_mutex_lock(&d->mux); + if (d == dom0) { + /* Take events off of dom0's control ring, and send + them to the event receivers. */ + while (d->tx_req_cons != d->ctrl_if->tx_req_prod) { + c = d->tx_req_cons % CONTROL_RING_SIZE; + id = d->ctrl_if->tx_ring[c].id; + evt = find_event_receiver(id); + if (evt != NULL) { + PRINTF(1, "delivering event id %d\n", evt->id); + pthread_cond_broadcast(&evt->cond); + pthread_mutex_unlock(&d->mux); + pthread_yield(); + pthread_mutex_lock(&d->mux); + } else { + warnx("unexpected message id %d discarded", + id); + d->tx_req_cons++; + } + } + while (d->rx_resp_cons != d->ctrl_if->rx_resp_prod) { + c = d->rx_resp_cons % CONTROL_RING_SIZE; + id = d->ctrl_if->rx_ring[c].id; + evt = find_event_receiver(id); + if (evt != NULL) { + PRINTF(1, "delivering event rep id %d\n", evt->id); + pthread_cond_broadcast(&evt->cond); + pthread_mutex_unlock(&d->mux); + pthread_yield(); + pthread_mutex_lock(&d->mux); + } else { + warnx("unexpected message reply id %d discarded", + id); + d->rx_resp_cons++; + } + } + } else { + if (d->plugged) { + d->event_pending = 1; + } else { + pthread_cond_broadcast(&d->cond); + } + } + pthread_mutex_unlock(&d->mux); +} + +static void +handle_evtchn_event(void) +{ + short port; + struct domain *d; + + read(evtchn_fd, &port, sizeof(short)); + write(evtchn_fd, &port, sizeof(short)); + foreach_domain (d) { + if (d->control_evtchn == port) { + signal_domain(d); + return; + } + } + warnx("got an event on an unknown port %d", port); +} + +void * +map_domain_mem(struct domain *d, unsigned long mfn) +{ + return xc_map_foreign_range(xc_handle, d->domid, + PAGE_SIZE, PROT_READ | PROT_WRITE, + mfn); +} + +static void +handle_console_event(struct console_connection *cc) +{ + int r; + int fd; + + switch (cc->state) { + case CC_STATE_ERROR: + /* Errors shouldn't get here. */ + abort(); + case CC_STATE_PENDING: + fd = accept(cc->fd, NULL, NULL); + if (fd >= 0) { + PRINTF(3, "Accepted console connection for domain %d", + cc->dom->domid); + close(cc->fd); + cc->fd = fd; + cc->state = CC_STATE_CONNECTED; + while (cc->buf_used != 0) { + r = write(cc->fd, + cc->buf, + cc->buf_used); + if (r <= 0) { + cc->state = CC_STATE_ERROR; + break; + } + memmove(cc->buf, + cc->buf + r, + cc->buf_used - r); + cc->buf_used -= r; + } + free(cc->buf); + cc->buf = NULL; + cc->buf_allocated = 0; + } else { + PRINTF(1, "error %s accepting console", strerror(errno)); + } + pthread_mutex_unlock(&cc->dom->mux); + break; + case CC_STATE_CONNECTED: + if (cc->in_buf_allocated == 0) { + assert(cc->in_buf_used == 0); + cc->in_buf_allocated = 128; + cc->in_buf = xmalloc(cc->in_buf_allocated); + } + if (cc->in_buf_used == cc->in_buf_allocated) { + cc->in_buf_allocated *= 2; + cc->in_buf = xrealloc(cc->in_buf, cc->in_buf_allocated); + } + r = read(cc->fd, cc->in_buf + cc->in_buf_used, + cc->in_buf_allocated - cc->in_buf_used); + if (r <= 0) { + cc->state = CC_STATE_ERROR; + } else { + cc->in_buf_used += r; + } + pthread_mutex_unlock(&cc->dom->mux); + signal_domain(cc->dom); + break; + } +} + +static void +handle_connection_event(struct open_connection *oc) +{ + int r; + + /* We know that some amount of data is ready and waiting for + us. Slurp it in. */ + if (oc->buf_used == oc->buf_allocated) { + oc->buf_allocated *= 2; + oc->buf = xrealloc(oc->buf, oc->buf_allocated); + } + r = read(oc->fd, oc->buf + oc->buf_used, + oc->buf_allocated - oc->buf_used); + if (r < 0) { + warn("reading command from remote"); + oc->state = OC_STATE_ERROR; + } else if (r == 0) { + warnx("reading command from remote"); + oc->state = OC_STATE_ERROR; + } else { + oc->buf_used += r; + if (strchr(oc->buf, '\n')) + oc->state = OC_STATE_COMMAND_PENDING; + } +} + +static void +get_and_process_event(void) +{ + fd_set read_fds, except_fds; + struct open_connection *oc; + struct console_connection *cc; + int max_fd = listen_fd; + int r; + struct list_head *li, *temp_li; + + FD_ZERO(&read_fds); + FD_ZERO(&except_fds); + FD_SET(listen_fd, &read_fds); + FD_SET(evtchn_fd, &read_fds); + if (evtchn_fd > max_fd) + max_fd = evtchn_fd; + foreach_open_connection(oc) { + FD_SET(oc->fd, &read_fds); + FD_SET(oc->fd, &except_fds); + if (oc->fd > max_fd) + max_fd = oc->fd; + } + foreach_console_connection(cc) { + FD_SET(cc->fd, &read_fds); + FD_SET(cc->fd, &except_fds); + if (cc->fd > max_fd) + max_fd = cc->fd; + } + + r = select(max_fd + 1, &read_fds, NULL, &except_fds, NULL); + if (r < 0) + err(1, "select"); + if (FD_ISSET(listen_fd, &read_fds)) { + accept_new_connection(); + } else if (FD_ISSET(evtchn_fd, &read_fds)) + handle_evtchn_event(); + + + foreach_open_connection(oc) { + if (FD_ISSET(oc->fd, &read_fds)) + handle_connection_event(oc); + if (FD_ISSET(oc->fd, &except_fds)) + oc->state = OC_STATE_ERROR; + } + list_foreach_safe(&head_console, li, temp_li) { + cc = list_item(li, struct console_connection, list); + if (FD_ISSET(cc->fd, &read_fds)) + handle_console_event(cc); + if (FD_ISSET(cc->fd, &except_fds) || + cc->state == CC_STATE_ERROR) { + PRINTF(1, "Cleaning up console connection"); + cc->dom->cc = NULL; + list_remove(&cc->list); + close(cc->fd); + if (cc->buf_allocated != 0) + free(cc->buf); + if (cc->in_buf_allocated != 0) + free(cc->in_buf); + free(cc); + } + } + + /* Run pending stuff on the open connections. */ + list_foreach_safe(&head_connection, li, temp_li) { + oc = list_item(li, struct open_connection, connection_list); + switch (oc->state) { + case OC_STATE_ERROR: + list_remove(&oc->connection_list); + closedown_connection(oc); + break; + case OC_STATE_COMMAND_PENDING: + process_command(oc); + break; + case OC_STATE_CONNECTED: + /* Don't need to do anything */ + break; + } + } +} + +static int +start_listening(void) +{ + int sock; + struct sockaddr_in inaddr; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) + err(1, "creating socket"); + memset(&inaddr, 0, sizeof(inaddr)); + inaddr.sin_family = AF_INET; + inaddr.sin_port = htons(MINIXEND_PORT); + + if (bind(sock, (struct sockaddr *)&inaddr, sizeof(inaddr)) < 0) + err(1, "binding to port %d", MINIXEND_PORT); + if (listen(sock, 5) < 0) + err(1, "listening for connections"); + + return sock; +} + +static struct domain * +find_dom0(void) +{ + int r; + xc_dominfo_t info; + struct domain *work; + + r = xc_domain_getinfo(xc_handle, 0, 1, &info); + if (r < 0) + err(1, "getting domain 0 information"); + work = xmalloc(sizeof(*work)); + work->control_evtchn = 2; + if (ioctl(evtchn_fd, EVTCHN_BIND, 2) < 0) + err(1, "binding to domain 0 control event channel"); + + work->domid = 0; + work->name = xstrdup("dom0"); + work->mem_kb = info.max_memkb; + work->state = DOM_STATE_RUNNING; + work->shared_info_mfn = info.shared_info_frame; + + work->shared_info = map_domain_mem(work, info.shared_info_frame); + work->ctrl_if = (control_if_t *)((unsigned)work->shared_info + 2048); + work->tx_req_cons = work->ctrl_if->tx_req_prod; + work->rx_resp_cons = work->ctrl_if->rx_resp_prod; + + pthread_mutex_init(&work->mux, NULL); + pthread_cond_init(&work->cond, NULL); + + list_insert_after(&work->domain_list, &head_domain); + + return work; +} + +int +main(int argc, char *argv[]) +{ + int r; + + r = our_system("/etc/xen/network start antispoof=no"); + if (r < 0) + err(1, "running /etc/xen/network"); + if (!WIFEXITED(r)) { + if (WIFSIGNALED(r)) { + errx(1, "/etc/xen/network killed by signal %d", + WTERMSIG(r)); + } + errx(1, "/etc/xen/network terminated abnormally"); + } + if (WEXITSTATUS(r) != 0) + errx(1, "/etc/xen/network returned error status %d", + WEXITSTATUS(r)); + + xc_handle = xc_interface_open(); + + listen_fd = start_listening(); + + evtchn_fd = open("/dev/xen/evtchn", O_RDWR); + if (evtchn_fd < 0) + err(1, "openning /dev/xen/evtchn"); + + dom0 = find_dom0(); + + while (1) { + get_and_process_event(); + + PRINTF(5, "Dom0 ring state:\n"); + PRINTF(5, "RX: req_prod %ld, resp_prod %ld, resp_cons %ld\n", + dom0->ctrl_if->rx_req_prod, + dom0->ctrl_if->rx_resp_prod, + dom0->rx_resp_cons); + PRINTF(5, "TX: req_prod %ld, resp_prod %ld, req_cons %ld\n", + dom0->ctrl_if->tx_req_prod, + dom0->ctrl_if->tx_resp_prod, + dom0->tx_req_cons); + } + + return 0; +} + diff --git a/tools/x2d2/minixend.h b/tools/x2d2/minixend.h new file mode 100644 index 0000000000..1dc4cee9f4 --- /dev/null +++ b/tools/x2d2/minixend.h @@ -0,0 +1,218 @@ +#ifndef MINIXEND_H__ +#define MINIXEND_H__ + +#include +#include +#include "domain_controller.h" + +/* Yet again, persuading Xen headers to include successfully in a + userspace process proves to be beyond me. Sigh. */ +#define MAX_VIRT_CPUS 1 + +typedef struct { + u32 tsc_bits; /* 0: 32 bits read from the CPU's TSC. */ + u32 tsc_bitshift; /* 4: 'tsc_bits' uses N:N+31 of TSC. */ +} PACKED tsc_timestamp_t; /* 8 bytes */ + +typedef struct { + u64 mfn_to_pfn_start; /* MFN of start of m2p table */ + u64 pfn_to_mfn_frame_list; /* MFN of a table of MFNs that + make up p2m table */ +} PACKED arch_shared_info_t; + +typedef struct +{ + unsigned long ebx; + unsigned long ecx; + unsigned long edx; + unsigned long esi; + unsigned long edi; + unsigned long ebp; + unsigned long eax; + unsigned long ds; + unsigned long es; + unsigned long fs; + unsigned long gs; + unsigned long _unused; + unsigned long eip; + unsigned long cs; + unsigned long eflags; + unsigned long esp; + unsigned long ss; +} PACKED execution_context_t; + +typedef struct shared_info_st +{ + struct { + u8 evtchn_upcall_pending; + u8 evtchn_upcall_mask; + u8 pad0, pad1; + } PACKED vcpu_data[MAX_VIRT_CPUS]; /* 0 */ + u32 evtchn_pending[32]; /* 4 */ + u32 evtchn_pending_sel; /* 132 */ + u32 evtchn_mask[32]; /* 136 */ + u64 cpu_freq; /* 264: CPU frequency (Hz). */ + u32 time_version1; /* 272 */ + u32 time_version2; /* 276 */ + tsc_timestamp_t tsc_timestamp; /* TSC at last update of time vals. */ + u64 system_time; /* Time, in nanosecs, since boot. */ + u32 wc_sec; /* Secs 00:00:00 UTC, Jan 1, 1970. */ + u32 wc_usec; /* Usecs 00:00:00 UTC, Jan 1, 1970. */ + u64 domain_time; /* Domain virtual time, in nanosecs. */ + u64 wall_timeout; /* 312 */ + u64 domain_timeout; /* 320 */ + execution_context_t execution_context; /* 328 */ + arch_shared_info_t arch; +} PACKED shared_info_t; + +/* End of stuff which belongs in a Xen header */ + + +struct list_head { + struct list_head *next, **pprev; +}; + +struct open_connection { + struct list_head connection_list; + int fd; + enum { + OC_STATE_CONNECTED, + OC_STATE_ERROR, + OC_STATE_COMMAND_PENDING + } state; + + /* Buffer of stuff coming from the remote until we get a whole + command */ + int buf_used; + int buf_allocated; + char *buf; +}; + +struct console_connection; + +/* Only ever accessed from the domain's controlling thread, unless + it's dom0, in which case we perform a moderately complex dance to + avoid needing any sort of locking at all. */ +struct domain { + struct list_head domain_list; + int control_evtchn; /* the local port for the doain control + interface event channel. */ + int domid; + char *name; + int mem_kb; + enum { + DOM_STATE_CREATED, /* created but not built */ + DOM_STATE_PAUSED, /* built but not started or paused */ + DOM_STATE_RUNNING, /* running normally */ + DOM_STATE_DEAD /* dead; either destroyed, crashed, + or exitted. */ + } state; + + unsigned long shared_info_mfn; + shared_info_t *shared_info; + control_if_t *ctrl_if; + CONTROL_RING_IDX tx_req_cons; + CONTROL_RING_IDX rx_resp_cons; + + unsigned created_netif_backend:1; + unsigned plugged:1; + unsigned event_pending:1; /* True if an event arrived while + the domain was plugged. */ + + struct console_connection *cc; + + char netif_mac[6]; + + /* Used for two purposes: waking up domain threads when + necessary, and synchronising access to dom0, which doesn't + have a domain thread. */ + pthread_mutex_t mux; + pthread_cond_t cond; + + pthread_t thread; +}; + +struct console_connection { + struct list_head list; + int fd; + struct domain *dom; + + enum { + CC_STATE_PENDING, + CC_STATE_CONNECTED, + CC_STATE_ERROR + } state; + + unsigned buf_allocated; + unsigned buf_used; + char *buf; + + unsigned in_buf_allocated; + unsigned in_buf_used; + char *in_buf; +}; + + +void *domain_thread_func(void *d); +void process_command(struct open_connection *oc); + +void *xmalloc(size_t s); +void *xrealloc(void *x, size_t s); +char *xstrdup(const char *s); + +int allocate_event_channel(struct domain *d, int event_ports[2]); +void *map_domain_mem(struct domain *d, unsigned long mfn); +void signal_domain(struct domain *d); +int our_system(const char *fmt, ...); + +extern unsigned xc_handle; +#define EVTCHN_BIND _IO('E', 2) +extern int evtchn_fd; + +#define list_item(head, type, field) \ +((type *)((unsigned)(head) - offsetof(type, field))) + +#define foreach_item(iter, head, type, field) \ +for ((iter) = list_item((head)->next, type, field); \ + (iter) != list_item((head), type, field); \ + (iter) = list_item((iter)->field.next, type, field)) + +#define list_insert_after(what, head) \ +do { \ + (what)->next = (head)->next; \ + (what)->pprev = &(head)->next; \ + (head)->next->pprev = &(what)->next; \ + (head)->next = what; \ +} while (0) + +#define list_remove(head) \ +(head)->next->pprev = (head)->pprev; \ +*(head)->pprev = (head)->next; + +#define list_foreach_safe(head, li, temp) \ +for ((li) = (head)->next, (temp) = (li)->next; \ + (li) != (head); \ + (li) = (temp), (temp) = (li)->next) + +#define LIST_HEAD(x) { (x), &(x)->next } + + +extern struct list_head head_domain; +extern struct list_head head_console; + +#define foreach_domain(d) \ +foreach_item(d, &head_domain, struct domain, domain_list) +#define foreach_console_connection(cc) \ +foreach_item(cc, &head_console, struct console_connection, list) + + +#define CURRENT_LOG_LEVEL 0 + +#define PRINTF(level, ...) \ +do { \ + if ((level) >= CURRENT_LOG_LEVEL) \ + printf(__VA_ARGS__); \ +} while (0) + + +#endif /* MINIXEND_H__ */ diff --git a/tools/x2d2/util.c b/tools/x2d2/util.c new file mode 100644 index 0000000000..9994c7dfb8 --- /dev/null +++ b/tools/x2d2/util.c @@ -0,0 +1,132 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void * +xmalloc(size_t s) +{ + void *x; + + x = malloc(s); + if (x == NULL) + err(1, "allocating memory"); + memset(x, 0, s); + return x; +} + +void * +xrealloc(void *x, size_t s) +{ + void *y; + y = realloc(x, s); + if (y == NULL) + err(1, "allocating more memory"); + return y; +} + +char * +xstrdup(const char *s) +{ + char *x = strdup(s); + if (x == NULL) + err(1, "duplicating %s", s); + return x; +} + +/* Slightly less stupid implementation of system(). We return + negative iff there is an error executing the shell; otherwise, we + return the wait status as reported by waitpid(). Also, we support + printf-style escapes. We don't handle setting the SIGCHLD handler + to SIGIGN, though: in that case, we have a race. */ +int +our_system(const char *fmt, ...) +{ + char *cmd = NULL; + int r; + va_list ap; + pid_t child = -1; + int pip[2] = {-1, -1}; + int e; + fd_set fds; + struct timeval to; + int res; + pid_t c; + unsigned status; + + va_start(ap, fmt); + r = vasprintf(&cmd, fmt, ap); + va_end(ap); + if (r < 0) + return r; + r = pipe(pip); + if (r < 0) { + res = r; + goto out; + } + child = fork(); + if (child < 0) { + res = child; + goto out; + } + if (child == 0) { + close(pip[0]); + fcntl(pip[1], F_SETFD, 1); + r = execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); + /* Uh oh, exec failed */ + write(pip[1], &r, sizeof(r)); + _exit(1); + } + + close(pip[1]); + pip[1] = -1; + + c = waitpid(child, &status, 0); + if (c < 0) { + res = c; + goto out; + } + assert(c == child); + child = -1; + + /* Check execl result */ + FD_ZERO(&fds); + FD_SET(pip[0], &fds); + memset(&to, 0, sizeof(to)); + r = select(pip[0]+1, &fds, NULL, NULL, &to); + if (r == 0) { + res = status; + } else { + assert(FD_ISSET(pip[0], &fds)); + r = read(pip[0], &res, sizeof(res)); + if (r != sizeof(res)) + res = status; + } + close(pip[0]); + pip[0] = -1; + + out: + e = errno; + if (child >= 0) { + /* Not obvious what the correct thing to do here is. */ + /* Don't want to kill the child; that will create a + zombie. */ +// kill(child, 9); + } + if (pip[0] >= 0) + close(pip[0]); + if (pip[1] >= 0) + close(pip[1]); + free(cmd); + errno = e; + return res; +} -- 2.30.2